/*
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */
package com.xflib.framework.util;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.text.StringSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 读取resources下的yaml配置类
 * <p>
 * 使用方法
 * eg:
 * zdc:
 * config:
 * key: value
 * list:
 * - 张三
 * - 李四
 * map:
 * - name: 张三map
 * age: 12
 * - name: 李四map
 * age: 121
 * <p>
 * 支持获取指定层级之后的数据,如"zdc.config" : new ConfigBean().prefix("zdc.config").getObj()
 * 支持获取list中指定数据,如"zdc.config.list.1" : new ConfigBean().prefix("zdc.config.list.1").getObj()
 * 支持获取list中指定数据之后的数据,如"zdc.config.map.1.name" :new ConfigBean().prefix("zdc.config.list.1").getString()
 *
 * @author koradji
 */
public class YamlUtil {
    Logger logger = LoggerFactory.getLogger(YamlUtil.class);

    /**
     * 读取的资源名
     */
    private String fileName = "application.yml";
    /**
     * 获取的对象
     */
    private Object temp;
    /**
     * 原始对象(初次读入 application.yml 的缓存)
     */
    private Object origin;
//    /**
//     * kv 存入 properties
//     */
//    private Properties properties = new Properties();

    /**
     * 创建一个资源获取对象
     */
    public YamlUtil() {
        this.load();
    }

    /**
     * 创建一个资源获取对象,默认当前目录下的fileName文件
     *
     * @param fileName
     */
    public YamlUtil(String fileName) {
        this.fileName = fileName;
        this.load();
    }

    /**
     * 加载指定的文件
     */
    private YamlUtil load() {

        logger.info("Configuration loaded from: {}", this.fileName);
        Yaml yaml = new Yaml();
        try {
            // 读入
            InputStream inputStream1 = new FileInputStream(this.fileName);
            this.origin = yaml.load(inputStream1);
        } catch (Exception e) {
            throw new RuntimeException("文件不存在: " + this.fileName);
        }

        // 转换
        // TODO: 分析变量过程无法处理嵌套，需要优化
        String xxx = yaml.dumpAsMap(this.origin);
        Set<String> result=new HashSet<>();
        Pattern r = Pattern.compile("\\$\\{([^}]*)\\}");  //  ${}
        Matcher m = r.matcher(xxx);

        while(m.find()){
            result.add(m.group());
        }
        AtomicReference<String> zzz = new AtomicReference<>(xxx);
        result.forEach(t->{
            String key=t.substring(2,t.length() -1);
            String v = this.get(key);
            zzz.set(StrUtil.replace(zzz.get(), t, v));
        });

        // 转换后
        this.origin = yaml.load(new ByteArrayInputStream(zzz.get().getBytes()));
        //this.properties = flat2Properties("");

        return this;
    }

    /**
     * eg "zdc.config.list"
     * eg ""
     *
     * @param prefix
     */
    public YamlUtil prefix(String prefix) {

        if (prefix == null || "".equals(prefix.trim())) {
            return this;
        }
        this.temp = this.origin;
        //获取层级 key
        String[] keys = prefix.trim().split("\\.");
        for (String key : keys) {
            //判断数据类型
            if (this.temp instanceof Map) {
                this.temp = ((Map) this.temp).get(key);
            } else if (this.temp instanceof List) {
                if (isNumeric(key)) {
                    this.temp = ((List) this.temp).get(Integer.parseInt(key));
                } else {
                    throw new RuntimeException(String.format("当前层级类型为List,不能使用[%s]获取子集数据", key));
                }
            } else {
                throw new RuntimeException("暂时没有解析该类型或不支持再次解析");
            }
        }
        return this;
    }

    /**
     * 返回对象类型的数据,可能是List,Map,Obj
     *
     * @return
     */
    public Object getObj() {
        return this.temp;
    }

    /**
     * 返回Map类型的数据
     *
     * @return
     */
    public Map getMap() {
        if (this.temp instanceof Map) {
            return (Map) this.temp;
        }
        return null;
    }

    /**
     * 返回List类型的数据
     *
     * @return
     */
    public List getList() {
        if (this.temp instanceof List) {
            return (List) this.temp;
        }
        return null;
    }

    /**
     * 从 指定的 节点 反序列化
     *
     * @param clazz
     * @param prefix
     * @param <T>
     * @return T
     * @throws Exception
     */
    public <T> T load(Class<T> clazz, String prefix) {
        LoaderOptions loaderOptions = new LoaderOptions();
        Constructor constructor = new Constructor(clazz, loaderOptions);
        PropertyUtils propertyUtils = new PropertyUtils(){
            @Override
            public Property getProperty(Class<?> type, String name, BeanAccess bAccess) {
                // 转驼峰
                if ( name.indexOf('-') > -1 ) {
                    name = com.xflib.framework.util.StrUtil.camelize(name);
                }

                // 关键代码 忽略yaml中无法在类中找到属性的字段
                setSkipMissingProperties(true);

                return super.getProperty(type, name, bAccess);
            }
        };
        propertyUtils.setSkipMissingProperties(true);
        constructor.setPropertyUtils(propertyUtils);

        Yaml yaml = new Yaml(loaderOptions);
        String o = yaml.dump(prefix(prefix).getMap());
        return new Yaml(constructor).load(o);
    }

    /**
     * 返回String类型的数据
     *
     * @return
     */
    public String getString() {
        return this.temp == null ? "" : this.temp.toString();
    }

    /**
     * 返回Integer类型的数据
     *
     * @return
     */
    public Integer getInteger() {
        String string = getString();
        return string != null ? Integer.parseInt(string) : null;
    }

    /**
     * 字符串参数替换${key}
     * 此处代码可以放到自己的工具类里面
     *
     * @param message  字符串
     * @param paramMap 参数集合
     * @return 字符串
     */
    public static String replaceParams(String message, Map<String, Object> paramMap) {
        if (ObjUtil.isNotEmpty(paramMap)) {
            for (String key : paramMap.keySet()) {
                message = StrUtil.replace(message, "${" + key + "}", (String) paramMap.get(key));
            }
        }
        return message;
    }

    //TODO 可以自定也解析其他类型

    /**
     * 判断是否是数字
     *
     * @param cs
     * @return
     */
    public static boolean isNumeric(final CharSequence cs) {
        if (cs == null || cs.length() == 0) {
            return false;
        }
        final int sz = cs.length();
        for (int i = 0; i < sz; i++) {
            if (!Character.isDigit(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }


    /*
     * 一下方法用于 yaml -> properties
     */

    /**
     * 获取key的值，支持深度取值如 user.address.country 从
     * {"user":{"name":"jack","age":24, "address":{"country":"China", "detail":"Hunan Changsha"}}}
     * 获得的结果为 China
     * 支持Map数组和list
     *
     * @param key
     * @return
     */
    public <T> T get(String key) {
        T result;
        result = (T) this.get(key, (Map) this.origin);
        // 变量替换
        return getReal((Map) this.origin, result);
    }

    public static <T> T getReal(Map origin, Object var) {
        T result;
        if (var instanceof String) {
            String resultStr = String.valueOf(var);
            if (resultStr.contains(StringSubstitutor.DEFAULT_VAR_START)) {
                StringSubstitutor stringSubstitutor = new StringSubstitutor((Map) origin);
                result = (T) stringSubstitutor.replace(resultStr);
            } else {
                result = (T) var;
            }
        } else {
            result = (T) var;
        }
        return result;
    }

    private <T> T get(String key, Map<String, T> source) {
        String[] keys = key.split("\\.");
        String currentKeyFull = keys[0];    // 当前key全名，可能包含索引[?]

        String currentKey = currentKeyFull; // 当前key
        int currentIndex = -1;           // 指定索引
        Pattern p = Pattern.compile("(\\w+)\\[?(\\d+)?\\]?");
        Matcher m = p.matcher(currentKeyFull);
        if (m.find()) {
            currentKey = m.group(1);
            String indexStr = m.group(2);
            if (StrUtil.isNotEmpty(indexStr) && StrUtil.isNumeric(indexStr)) {
                currentIndex = Integer.valueOf(indexStr);
            }
        }
        String nextKey = String.join(".", Arrays.copyOfRange(keys, 1, keys.length)); // 余下未处理的key

        T result = source.get(currentKey);

        if (StrUtil.isNotBlank(currentKey) && StrUtil.isNotBlank(nextKey)) {
            if (result instanceof Map) {
                return (T) get(nextKey, (Map) result);
            } else if (result instanceof List) { // list
                Object result1 = ((List) result).get(currentIndex);
                if (result1 instanceof Map) {
                    return (T) get(nextKey, (Map) result1);
                }
                return (T) result1;
            } else if (result.getClass().isArray()) { // 数组
                Object[] result1 = (Object[]) result;
                if (result1[currentIndex] instanceof Map) {
                    return (T) get(nextKey, (Map) result1[currentIndex]);
                }
                return (T) result1;
            }
        }

        if (currentIndex > -1) {
            if (result instanceof List) {
                return (T) ((List) result).get(currentIndex);
            } else if (result.getClass().isArray()) {
                return (T) ((Object[]) result)[currentIndex];
            }
        }

        return result;
    }


    private static final String SEPARATOR = ".";

    /**
     * 深度嵌套map对象转大map（扁平化）
     *
     * @param source     源map
     * @param parentNode 父节点扁平化之后的名字
     * @return map
     */
    public static Map<String, Object> flat(Map<String, Object> source, String parentNode) {
        Map<String, Object> flat = new HashMap<>();
        Set<Map.Entry<String, Object>> set = source.entrySet();
        String prefix = StrUtil.isNotBlank(parentNode) ? parentNode + SEPARATOR : "";
        set.forEach(entity -> {
            Object value = entity.getValue();
            String key = entity.getKey();
            String newKey = prefix + key;
            if (value instanceof Map) {
                flat.putAll(flat((Map) value, newKey));
            } else if (value instanceof Collection) {
                Iterator iterable = ((Collection) value).iterator();
                int i = 0;
                while (iterable.hasNext()) {
                    Object obj = iterable.next();
                    if (obj instanceof Map) {
                        flat.putAll(flat((Map) obj, newKey + "[" + i + "]"));
                    } else {
                        flat.put(newKey, obj);
                    }
                    i++;
                }
            } else {
                flat.put(newKey, value);
            }
        });
        return flat;
    }

    public Map<String, Object> flat(String parentNode) {
        return flat((Map<String, Object>) this.origin, parentNode);
    }

}

